/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.config.console.client;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import org.tianyun.cloud.config.console.client.response.ConfigRequest;
import org.tianyun.cloud.config.console.client.response.ConfigResponse;
import org.tianyun.cloud.config.console.configuration.ConsoleConfigProperties;
import org.tianyun.cloud.utils.JsonUtils;
import org.tianyun.cloud.utils.StreamUtils;

import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 控制台配置中心服务
 *
 * @author ebert_chan
 */
public class ConsoleConfigServer {

    private static final Logger log = LoggerFactory.getLogger(ConsoleConfigServer.class);

    private static AtomicBoolean ready = new AtomicBoolean(false);

    private static ConsoleConfigServer client;

    private ConsoleConfigProperties consoleConfigProperties;

    private RestTemplate fetchRestTemplate;

    private RestTemplate postRestTemplate;

    private ConfigResponse lastResponseCache;

    private AtomicBoolean enabledCache = new AtomicBoolean(false);

    ConsoleConfigServer(ConsoleConfigProperties consoleConfigProperties) {
        this.consoleConfigProperties = consoleConfigProperties;
        this.fetchRestTemplate = new RestTemplateBuilder().setConnectTimeout(Duration.ofMillis(consoleConfigProperties.getConnectTimeout())).setReadTimeout(Duration.ofMillis(consoleConfigProperties.getReadTimeout())).build();
        this.postRestTemplate = new RestTemplateBuilder().setConnectTimeout(Duration.ofMillis(5000)).setReadTimeout(Duration.ofMillis(10000)).build();
    }

    private ConsoleConfigProperties getConsoleConfigProperties() {
        return consoleConfigProperties;
    }

    private ConfigResponse fetch(String serverAddr) {
        ConfigResponse configResponse = this.fetchRestTemplate.getForObject(serverAddr, ConfigResponse.class);
        return configResponse;
    }

    private void post(String serverAddr, ConfigResponse configResponse) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

        String params = JsonUtils.toJsonString(configResponse != null ? configResponse : Collections.emptyMap());
        HttpEntity<String> httpEntity = new HttpEntity<String>(params, headers);

        this.postRestTemplate.postForEntity(serverAddr, httpEntity, String.class);
    }

    private String getFetchServerAddr(String application) {
        return StreamUtils.url(client.consoleConfigProperties.getServerAddr(), "/console/config-pull", application);
    }

    private String getPostServerAddr() {
        return StreamUtils.url(client.consoleConfigProperties.getServerAddr(), "/console/config-push");
    }

    public static void init(ConsoleConfigProperties consoleConfigProperties) {
        if (ready.compareAndSet(false, true)) {
            client = new ConsoleConfigServer(consoleConfigProperties);
            log.info("ConsoleConfigServer initialization completed.");
        }
    }

    public static void pushConfig(ConfigRequest config) {
        ConsoleConfigProperties consoleConfigProperties = client.getConsoleConfigProperties();

        int retryNum = 0;
        do {
            String serverAddr = client.getPostServerAddr();
            try {
                client.post(serverAddr, config);
                log.info("Push configuration successfully, server-addr: {}", serverAddr);
                break;
            } catch (Exception e) {
                log.warn("Failed to push configuration from console, server-addr: {}", serverAddr);
                retryNum++;
                if (retryNum >= consoleConfigProperties.getConfigMaxRety()) {
                    break;
                }
            }
            try {
                Thread.sleep(consoleConfigProperties.getConfigRetyTime());
            } catch (InterruptedException e) {
                // ignore e
            }
        } while (retryNum < consoleConfigProperties.getConfigMaxRety());
    }

    public static ConfigResponse getConfig(String application) {
        ConsoleConfigProperties consoleConfigProperties = client.getConsoleConfigProperties();

        int retryNum = 0;
        do {
            String serverAddr = client.getFetchServerAddr(application);
            try {
                client.lastResponseCache = client.fetch(serverAddr);
                client.enabledCache.compareAndSet(false, true);
                log.info("Pull configuration successfully, server-addr: {}", serverAddr);
                break;
            } catch (Exception e) {
                retryNum++;
                if (retryNum >= consoleConfigProperties.getConfigMaxRety()) {
                    if (client.enabledCache.get()) {
                        log.warn("Failed to pull configuration from console, default use of the previous cached result. server-addr: {}", serverAddr);
                        break;
                    }
                    if (consoleConfigProperties.isSkipFailure()) {
                        log.warn("Failed to pull configuration from console, server-addr: {}", serverAddr);
                        break;
                    }
                    throw new RuntimeException("Application failed to connect to console config server, server-addr: " + serverAddr, e);
                }
                log.warn("Failed to pull configuration from console, server-addr: {}", serverAddr);
            }
            try {
                Thread.sleep(consoleConfigProperties.getConfigRetyTime());
            } catch (InterruptedException e) {
                // ignore e
            }
        } while (retryNum < consoleConfigProperties.getConfigMaxRety());

        return client.lastResponseCache;
    }

}
